{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Breaking Software ECC <u>without</u> TraceWhisperer"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Background\n",
    "\n",
    "The [uecc_part1_trace.ipynb](uecc_part1_trace.ipynb) notebook was written to show how to find and exploit side-channel leakage in the [micro-ecc library](https://github.com/newaetech/chipwhisperer/tree/develop/hardware/victims/firmware/crypto/micro-ecc) library using Arm trace and [TraceWhisperer](https://github.com/newaetech/tracewhisperer).\n",
    "\n",
    "Arm trace [is](https://developer.arm.com/documentation/ihi0029/latest/)\n",
    "[very](https://developer.arm.com/documentation/ihi0014/q)\n",
    "[complex](https://developer.arm.com/documentation/ddi0403/ee); it's also only available on Arm targets. It seemed worthwhile to do a variant of uecc_part1_trace.ipynb which does not use trace, and so this notebook came to be.\n",
    "\n",
    "This notebook carries out what is essentially the same attack as uecc_part1_trace.ipynb, but using SAD instead of Arm trace to find the leakage.\n",
    "\n",
    "The two notebooks are similar and independent of one another; you may wish to do just one, or both:\n",
    "- If your main interest is in learning about ECC side-channel attacks, then you can start with either notebook.\n",
    "- If your main interest is in learning how Arm trace or SAD can be used to find vulnerable segments of power traces, then start with the corresponding notebook."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Supported Hardware\n",
    "\n",
    "This notebook is written specifically for **CW-Husky** and the **STM32F3** target.\n",
    "\n",
    "It is definitely possible to run this notebook on the SAM4S target, but you'll have to make some small tweaks. If you have a SAM4S, it's recommended as-is, on the STM32 target (if you don't have one, use the pre-recorded STM32 traces), to understand how the attack works; then, switch to your live SAM4S target, and use the hints provided in order to succeed (look for the \"**⚠️ SAM4S tip**\" notes that pop up throughout).\n",
    "\n",
    "It should also be possible (and encouraged!) to run on any other target (as long as the [micro-ecc library](https://github.com/newaetech/chipwhisperer/tree/develop/hardware/victims/firmware/crypto/micro-ecc) can be compiled), but in this case no hints are provided (in all likelihood, where SAM4S hints are provided is also were other targets will need their own tweaks).\n",
    "\n",
    "In principle, it should be possible to succeed with CW-Pro, but the CW-Pro lacks the trigger timestamping feature that is heavily used here to help fine-tuning SAD parameters.\n",
    "\n",
    "CW-Lite/Nano cannot be used since they have no SAD triggering capability.\n",
    "\n",
    "If you don't have the required hardware, you can still follow along the entire notebook with the pre-recorded traces, by setting `TRACES = 'SIMULATED'`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "PLATFORM = 'CW308_STM32F3'\n",
    "#PLATFORM = 'CW308_SAM4S'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "TRACES = 'HARDWARE' # if you have the required capture+target hardware: capture actual traces\n",
    "#TRACES = 'SIMULATED' # if you don't have capture+target hardware: use pre-captured traces (these traces were obtained using CW-Husky with a STM32F3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import chipwhisperer as cw\n",
    "\n",
    "if TRACES == 'SIMULATED':\n",
    "    # fake out the CW scope and target: this will allow us to set attributes and read back previously set attributes\n",
    "    scope = type('', (), {'adc': type('', (), {})(),\n",
    "                          'gain': type('', (), {})(),\n",
    "                          'io': type('', (), {})(),\n",
    "                          'UARTTrigger': type('', (), {})(),\n",
    "                          'SAD': type('', (), {})(),\n",
    "                          'trigger': type('', (), {})(),\n",
    "                          'trace': type('', (), {})(),\n",
    "                          'fpga_buildtime': type('', (), {})(),\n",
    "                          'clock': type('', (), {})()})()\n",
    "\n",
    "    target = type('', (), {'baud': type('', (), {})()})()\n",
    "\n",
    "else:\n",
    "    scope = cw.scope()\n",
    "    %run \"../../Setup_Scripts/Setup_Generic.ipynb\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.trace.target = target\n",
    "trace = scope.trace\n",
    "scope.clock.clkgen_freq = 10e6\n",
    "scope.clock.clkgen_src = 'system'\n",
    "scope.clock.adc_mul = 1\n",
    "target.baud = 38400 * 10 / 7.37\n",
    "scope.io.glitch_trig_mcx = 'trigger'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Attack Details\n",
    "\n",
    "Refer to the [Attack Details](uecc_part1_trace.ipynb#Attack-Details) section of uecc_part1_trace.ipynb for some background on the target code.\n",
    "\n",
    "In this notebook, since we're not using Arm trace to trigger, we instrument the code to help guide us:\n",
    "\n",
    "<table>\n",
    "<tr>\n",
    "    <th>Original uecc_part1_trace.ipynb target code</th>\n",
    "    <th>Modified target code</th>\n",
    "</tr>\n",
    "<tr>\n",
    "<td>\n",
    "  \n",
    "```C\n",
    "for (i = num_bits - 2; i > 0; --i) {\n",
    "\n",
    "\n",
    "    \n",
    "    nb = !uECC_vli_testBit(scalar, i);\n",
    "    XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);\n",
    "\n",
    "    \n",
    "    \n",
    "    XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);\n",
    "}\n",
    "```\n",
    "  \n",
    "</td>\n",
    "<td>\n",
    "\n",
    "```c\n",
    "    for (i = num_bits - 2; i > 0; --i) {\n",
    "#ifdef FW_TRIGGER\n",
    "        trigger_high();\n",
    "#endif\n",
    "        nb = !uECC_vli_testBit(scalar, i);\n",
    "        XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);\n",
    "#ifdef FW_TRIGGER\n",
    "        trigger_low();\n",
    "#endif\n",
    "        XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);\n",
    "    }\n",
    "\n",
    "```\n",
    "</td>\n",
    "</tr>\n",
    "</table>\n",
    "\n",
    "We'll first use a version of the firmware compiled with `FW_TRIGGER` defined, to help guide and build the attack; later we'll switch to the triggerless firmware build, for the actual realistic attack."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Program target:\n",
    "\n",
    "**Warning**: if you make any changes to the target firmware (including compiler version and switches), there is a chance that the attack parameters used in this notebook won't work for you anymore. So, for your first run-through, stick with the provided binary.\n",
    "\n",
    "But, making changes to the target firmware is a great way to get practice with side-channel attacks, so once you've had success with the provided firmware, do go ahead and try some changes!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#%%bash -s \"$PLATFORM\"\n",
    "#cd ../../.../firmware/mcu/simpleserial-ecc-notrace\n",
    "#make PLATFORM=$1 CRYPTO_TARGET=MICROECC FW_TRIGGER=1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fw_path = '../../../firmware/mcu/simpleserial-ecc-notrace/simpleserial-ecc-fwtrigger-{}.hex'.format(PLATFORM)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED':\n",
    "    if (PLATFORM == 'CW308_STM32F3') or (PLATFORM == 'CWLITEARM'):\n",
    "        prog = cw.programmers.STM32FProgrammer\n",
    "    elif PLATFORM == 'CW308_SAM4S':\n",
    "        prog = cw.programmers.SAM4SProgrammer\n",
    "\n",
    "    cw.program_target(scope, prog, fw_path)\n",
    "\n",
    "    reset_target(scope)\n",
    "    target.simpleserial_write('i', b'')\n",
    "    time.sleep(0.1)\n",
    "    print(target.read())\n",
    "\n",
    "    scope.clock.reset_adc()\n",
    "    time.sleep(0.2)\n",
    "    assert (scope.clock.adc_locked), \"ADC failed to lock\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Customized functions to run and capture ECC power traces:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run \"ECC_capture.ipynb\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## First trace\n",
    "\n",
    "To get our bearings, let's see what a trace looks like when $k_r$ has a very regular pattern. We'll use a random point."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# big block of 1's, big block of 0's:\n",
    "k = 0xf0000000fffffffefffffffffffffff04319055258e8617b0c46353d039cdaaf\n",
    "kr = regularized_k(k)\n",
    "hex(kr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We could capture a full target operation but let's just examine a subset from the middle of the operation which should be long enough to capture the processing of a few bits of $k$.\n",
    "\n",
    "(Feel free to extend the capture to the full trace, if you're curious.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.adc.samples = int(100e3)\n",
    "scope.adc.presamples = 0\n",
    "scope.adc.offset = int(2e6)\n",
    "scope.adc.segments = 1\n",
    "scope.adc.stream_mode = False\n",
    "scope.gain.db = 21"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **⚠️ SAM4S tip**: try a higher gain."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rtrace = capture_ecc_traces(k, N=1, step='1')[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.plotting import figure, show\n",
    "from bokeh.resources import INLINE\n",
    "from bokeh.io import output_notebook\n",
    "output_notebook(INLINE)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = figure(width=2000)\n",
    "s.line(list(range(len(rtrace.wave))), rtrace.wave)\n",
    "show(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As mentioned above, the initial version of the target firmware that we're using here asserts the GPIO4 trigger at every iteration of the main loop which iterates over the bits of $k$, in `ECCPoint_mult()` function of uECC.c:\n",
    "\n",
    "```c\n",
    "    for (i = num_bits - 2; i > 0; --i) {\n",
    "#ifdef FW_TRIGGER\n",
    "        trigger_high();\n",
    "#endif\n",
    "        nb = !uECC_vli_testBit(scalar, i);\n",
    "        XYcZ_addC(Rx[1 - nb], Ry[1 - nb], Rx[nb], Ry[nb], curve);\n",
    "#ifdef FW_TRIGGER\n",
    "        trigger_low();\n",
    "#endif\n",
    "        XYcZ_add(Rx[nb], Ry[nb], Rx[1 - nb], Ry[1 - nb], curve);\n",
    "    }\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There is lots of information contained in this trace.\n",
    "\n",
    "First, we see periodic activity with peaks occuring very regularly every ~2000 samples.\n",
    "\n",
    "We also see some shifts up and down every ~22K samples. Could these be directly tied to $k$? It turns out that these shifts are caused by the GPIO4 trigger activity (we'll show this later), so we won't try to directly build an attack on them (the final attack will be against a version of the firmware compiled with `FW_TRIGGER` undefined).\n",
    "\n",
    "But we *will* use the triggers to *help build* the attack. Husky has a nifty feature where if you're triggering multiple times, it will timestamp those triggers; we can use this to learn exactly when `trigger_high()` is being called.\n",
    "\n",
    "In order to get these timestamps, we must do a segmented capture (where we capture a trace segment on each trigger) of the full capture. Let's capture 200 samples on every trigger, hoping this will catch some leakage that we can use towards an attack:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.adc.segments = 255\n",
    "scope.adc.samples = 200\n",
    "scope.adc.presamples = 0\n",
    "scope.adc.offset = 0\n",
    "scope.adc.segment_cycle_counter_en = 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ftrace = capture_ecc_traces(k, N=1, step='2')[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The trigger times can be read back with `scope.trigger.get_trigger_times()`.\n",
    "\n",
    "`get_ttimes()` is a wrapper around that function; we use it to deal with pre-recorded trigger times when `TRACES = 'SIMULATED'`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "get_ttimes??"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ttimes = get_ttimes(step='2')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "len(ttimes), min(ttimes), max(ttimes)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This tells us two important things:\n",
    "\n",
    "1. The FW trigger fired 255 times, as expected (`len(ttimes)` is 254 because it's the time between *successive triggers* that is recorded).\n",
    "2. The time delta between triggers is not constant, but it has a fairly narrow range.\n",
    "\n",
    "One question that may come to mind is whether there is time-based leakage. Our special $k$ allows us to easily check if there is a statistical difference between the time it takes to process a bit of $k$ that is \"1\" vs a bit that is \"0\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.average(ttimes[:128]) / np.average(ttimes[128:])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If there is an actual difference, it is very small. We won't pursue this any further (note that if you repeat this for a different point `(Px, Py)`, you'll get a different result: execution times depend on **both** $k$ **and** $P$).\n",
    "\n",
    "Out of curiosity, let's annotate the trigger times on our earlier power trace:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "abs_trigger_times = []\n",
    "counter = 0\n",
    "for t in ttimes:\n",
    "    counter += t\n",
    "    if 2e6 < counter < 2.1e6: # this is the interval captured by our first trace\n",
    "        abs_trigger_times.append(counter-int(2e6))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.models import Span\n",
    "s = figure(width=2000)\n",
    "for t in abs_trigger_times:\n",
    "    s.renderers.extend([Span(location=t, dimension='height', line_color='black', line_width=2)])\n",
    "s.line(list(range(len(rtrace.wave))), rtrace.wave)\n",
    "show(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## First step of the attack: establish distinguishing markers\n",
    "\n",
    "\n",
    "Let's collect several traces so that we can average them and see if we can spot differences between when the target is processing a $k$ bit that is one versus a $k$ bit that is zero.\n",
    "\n",
    "To facilitate this, each trace uses the same $k$, but a different base point. Using a different point allows us to \"average out\" the contribution of the base point to the power trace, to better focus on the effect of $k$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.adc.segments = 255\n",
    "scope.adc.samples = 170\n",
    "scope.adc.presamples = 0\n",
    "scope.adc.offset = 0\n",
    "scope.adc.segment_cycle_counter_en = 0\n",
    "scope.adc.stream_mode = False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **⚠️ SAM4S tip**: the distinguishing markers are easier to find with a $kr$ that has alternating ones and zeros:\n",
    "> `kr = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`\n",
    "> \n",
    "> You'll have to modify the `avg_ones` and `avg_zeros` calculations accordingly (e.g. ones are from even-numbered segments, zeros from odd-numbered segments).\n",
    "> \n",
    "> Finally, increase N to 200 traces for better results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rtraces = capture_ecc_traces(k, N=30, step='3')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "samples = scope.adc.samples\n",
    "\n",
    "avg_trace = np.zeros(samples)\n",
    "\n",
    "for t in rtraces:\n",
    "    for i in range(1,255):\n",
    "        avg_trace += t.wave[i*samples:(i+1)*samples]\n",
    "\n",
    "avg_trace /= (255*len(rtraces))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "avg_ones = np.zeros(samples)\n",
    "avg_zeros = np.zeros(samples)\n",
    "\n",
    "for t in rtraces:\n",
    "    for i in range(128):\n",
    "        avg_ones += t.wave[i*samples:(i+1)*samples]\n",
    "        avg_zeros += t.wave[(i+127)*samples:(i+128)*samples]\n",
    "\n",
    "avg_ones /= (127*len(rtraces))\n",
    "avg_zeros /= (127*len(rtraces))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = figure(width=2000)\n",
    "\n",
    "xrange = list(range(len(avg_trace)))\n",
    "s.line(xrange, avg_trace-100, line_color=\"black\")\n",
    "s.line(xrange, avg_ones-100, line_color=\"red\")\n",
    "s.line(xrange, avg_zeros-100, line_color=\"blue\")\n",
    "s.line(xrange, (avg_ones - avg_zeros)*20, line_color=\"orange\")\n",
    "\n",
    "show(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We're looking for sample points which allow us to reliably distinguish between bits of $k$ that are 1 and bits of $k$ that are 0; the orange line suggests there are many!\n",
    "\n",
    "Let's grab the sample indices for the largest negative peak:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "THRESHOLD = -50/20\n",
    "poi = list(np.where((avg_ones - avg_zeros) < THRESHOLD)[0])\n",
    "print(poi)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we define a function to score each bit of $k$ using the sum of the power trace values at each point of `poi`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def calc_sumdata(poi, ptraces, trim=None):\n",
    "    if trim:\n",
    "        samples = trim\n",
    "    else:\n",
    "        samples = scope.adc.samples\n",
    "    sumdata = np.zeros(255)\n",
    "    for i in range(255):\n",
    "        for t in ptraces:\n",
    "            for p in poi:\n",
    "                sample = t.wave[i*samples+abs(p)]\n",
    "                if p >= 0:\n",
    "                    sumdata[i] += sample\n",
    "                else:\n",
    "                    sumdata[i] -= sample\n",
    "    return sumdata/len(ptraces)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "and plot the results:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sd = calc_sumdata(poi, rtraces)\n",
    "\n",
    "s2 = figure(width=2000)\n",
    "\n",
    "xrange = list(range(len(sd)))\n",
    "s2.line(xrange, sd, line_color=\"red\", line_width=2)\n",
    "\n",
    "show(s2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This seems to work really well: the difference between ones and zeros is extremely clear.\n",
    "\n",
    "Before we carry on with this, let's see what happens when $k$ has more 0/1 transitions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "kr = 0xf0ccccccccccccccccccccccccccccccaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0f\n",
    "k = input_k(kr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r01traces = capture_ecc_traces(k, N=30, step='4')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sd01 = calc_sumdata(poi, r01traces)\n",
    "\n",
    "s3 = figure(width=2000)\n",
    "\n",
    "xrange = list(range(len(sd)))\n",
    "s3.line(xrange, sd, line_color=\"blue\", line_width=2)\n",
    "s3.line(xrange, sd01, line_color=\"red\", line_width=2)\n",
    "\n",
    "show(s3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It looks like our first stab at a distinguisher works really well when $k$ has contiguous blocks of ones or zeros, but really badly when $k$ has alternating ones and zeros.\n",
    "\n",
    "Let's try the other, smaller peaks further down the trace, just before sample 100, between the two dotted vertical lines:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s.renderers.extend([Span(location=80, dimension='height', line_color='black', line_width=2, line_dash='dotted')])\n",
    "s.renderers.extend([Span(location=95, dimension='height', line_color='black', line_width=2, line_dash='dotted')])\n",
    "show(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's define start and stop points and thresholds from which we'll automatically extract the points of interest:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# adjust if needed!\n",
    "START=80\n",
    "STOP=95\n",
    "PTHRESH = 6/20\n",
    "NTHRESH = 8/20"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "⚠️ The settings above work for the specific `CW308_STM32F3` target which was used for our pre-recorded traces... however with a *different* `CW308_STM32F3`, leakage is found shifted a few samples to the right(!) Presumably this is because the STM32's are from different batches...\n",
    "\n",
    "For reference, this is what we find on the pre-recorded traces. Adjust your START, STOP, PHTRESH and NTHRESH values if what you got above looks different than this.\n",
    "![poi11](img/uecc_part2_poi.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "poi = list(np.where((avg_ones[START:STOP] - avg_zeros[START:STOP]) > PTHRESH)[0] + START)\n",
    "poi.extend(list(-(np.where((avg_ones[START:STOP] - avg_zeros[START:STOP]) < -NTHRESH)[0] + START)))\n",
    "print(poi)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# this is what you should get with the STM32 target:\n",
    "assert poi == [82, 89, 90, -84, -92]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sd = calc_sumdata(poi, rtraces)\n",
    "sd01 = calc_sumdata(poi, r01traces)\n",
    "\n",
    "s = figure(width=2000)\n",
    "\n",
    "xrange = list(range(len(sd)))\n",
    "s.line(xrange, sd + 5, line_color=\"blue\", line_width=2)\n",
    "s.line(xrange, sd01, line_color=\"red\", line_width=2)\n",
    "\n",
    "show(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we have something that works equally well for long sequences of static values and alternating values.\n",
    "\n",
    "It's noisier than the first `poi`, but the 0/1 transitions are all quite clear.\n",
    "\n",
    "We're off to the races!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **⚠️ SAM4S tip**: better results are obtained with:\n",
    "> ```\n",
    "> START=0\n",
    "> STOP=283\n",
    "> PTHRESH = 4/20\n",
    "> NTHRESH = 5/20\n",
    "> ```\n",
    "> which should yield something similar to `poi = [24, 121, 122, 125, 126, 127, 153, 162, 167, -130, -131, -148, -150, -198, -246, -247]`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# The Attack\n",
    "This time let's play for real: we'll generate a random $k$ and see whether our attack can retrieve it.\n",
    "\n",
    "Since we only need a few samples, let's trim down our capture by pushing it out 80 samples and capturing only around the POI (for storage efficiency).\n",
    "\n",
    "> **⚠️ SAM4S tip**: adjust according to the range of your `poi` (or if you needed to adjust `START` and `STOP` from their defaults)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.adc.samples = 13\n",
    "scope.adc.offset = 80"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# adjust POI accordingly:\n",
    "for i in range(len(poi)):\n",
    "    if poi[i] > 0:\n",
    "        poi[i] -= 80\n",
    "    else:\n",
    "        poi[i] += 80"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "poi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "k = random_k()\n",
    "kr = regularized_k(k)\n",
    "hex(k), hex(kr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **⚠️ SAM4S tip**: the attack tends to require a few more traces (around 150) to succeed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ptraces = capture_ecc_traces(k, N=60, step='5')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# In the case of pre-recorded traces, set k to what was used for those traces:\n",
    "if TRACES == 'SIMULATED':\n",
    "    k = ptraces[0].textin['k']\n",
    "    kr = regularized_k(k)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sd = calc_sumdata(poi, ptraces)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = figure(width=2000)\n",
    "\n",
    "xrange = list(range(len(sd)))\n",
    "s.line(xrange, sd, line_color=\"red\", line_width=2)\n",
    "\n",
    "show(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If your plot looks good (where \"looks good\" = a sequence of ~well-distinguished highs and lows, approaching a noisy square wave), proceed to guessing all the bits.\n",
    "\n",
    "Otherwise, you may need to tweak your POI set (use the recommended values), or simply try a fresh trace acquisition."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def attack(poi, straces, trim=None, verbose=True):\n",
    "    sd = calc_sumdata(poi, straces, trim=trim)\n",
    "\n",
    "    # guess all bits from waveform:\n",
    "    guess = ''\n",
    "    for i in range(1,255):\n",
    "        if sd[i] > np.average(sd):\n",
    "            guess += '1'\n",
    "        else:\n",
    "            guess += '0'\n",
    "\n",
    "    # first and last bit are unknown, so enumerate the possibilities:\n",
    "    guesses = []\n",
    "    for first in (['0', '1']):\n",
    "        for last in (['0', '1']):\n",
    "            guesses.append(int(first + guess + last, 2))\n",
    "\n",
    "    kr = regularized_k(k)\n",
    "    wrong_bits = []\n",
    "    if kr in guesses:\n",
    "        if verbose: print('✅ Guessed right!')\n",
    "    else:\n",
    "        for kbit in range(1,254):\n",
    "            if int(guess[kbit-1]) != ((kr >> (255-kbit)) & 1):\n",
    "                wrong_bits.append(255-kbit)\n",
    "        if verbose:\n",
    "            print('Attack failed.')\n",
    "            print('Guesses: %s' % hex(guesses[0]))\n",
    "            print('         %s' % hex(guesses[1]))\n",
    "            print('         %s' % hex(guesses[2]))\n",
    "            print('         %s' % hex(guesses[3]))\n",
    "            print('Correct: %s' % hex(kr))\n",
    "            print('%d wrong bits' % len(wrong_bits))\n",
    "    return wrong_bits"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "attack(poi, ptraces)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The attack should have succeeded (or at least guess almost all of the 256 bits correctly).\n",
    "\n",
    "The last step is to see how well the attack works as we reduce the number of traces used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fwtrigger_wrong_bits = []\n",
    "for attack_traces in range(1, len(ptraces)+1):\n",
    "    print('Attacking with %d traces... ' % attack_traces,  end='')\n",
    "    wrong_bits = attack(poi, ptraces[:attack_traces], None, False)\n",
    "    if wrong_bits:\n",
    "        print('failed, %d wrong bits' % len(wrong_bits))\n",
    "    else:\n",
    "        print('passed ✅')\n",
    "    fwtrigger_wrong_bits.append(len(wrong_bits))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What's remarkable is that even with a single trace, most bits are guessed correctly."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Next step: adding SAD\n",
    "\n",
    "So we have a successful attack, but it's not very realistic because it relies on the firmware toggling the trigger line to tell us when it's processing each bit.\n",
    "\n",
    "Our ultimate goal is to remove the FW trigger, and we eventually will, but not yet. The next step is to identify the bits from a SAD pattern: we'll keep the FW trigger in place, but we won't actually use it.\n",
    "\n",
    "We re-use the traces from our previous captures to guide us. Let's overlay all of the 255 trace segments from a single target operation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.palettes import inferno\n",
    "from bokeh.plotting import figure, show\n",
    "from bokeh.resources import INLINE\n",
    "from bokeh.io import output_notebook\n",
    "from bokeh.models import Span, Legend, LegendItem\n",
    "import itertools\n",
    "output_notebook(INLINE)\n",
    "B = figure(width=1800)\n",
    "colors = itertools.cycle(inferno(255))\n",
    "for i in range(255):\n",
    "    B.line(list(range(samples)), rtraces[0].wave[i*samples:(i+1)*samples], color=next(colors))\n",
    "show(B)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We see that all the segments align very well until sample 151. It's no surprise that there is a divergence, since we know that the bits of $k$ are not processed in constant-time.\n",
    "\n",
    "This is very good, because a SAD-triggered capture needs trace segments that overlay nicely.\n",
    "\n",
    "To do a Husky-triggered SAD capture, we need two things:\n",
    "1. a reference waveform;\n",
    "2. a threshold.\n",
    "\n",
    "Husky SAD is an evolving thing: it keeps getting better! When this notebook was originally developed, we were limited to setting the SAD reference to 192 or 96 samples. Because of the divergence at sample 151, we used a 96-sample reference.\n",
    "\n",
    "Now Husky SAD is much more flexible: we could use 150 samples, but we'll stay with the original 96 samples. 96 is not a magic number: it's just what was at hand when this notebook was first developed.\n",
    "\n",
    "We could find a good SAD reference and threshold with trial and error on real captures; instead, let's compute SAD in software over our already-acquired traces:\n",
    "\n",
    "> **⚠️ SAM4S notes**: the divergence with the SAM4S is at sample 284. While a 96-sample reference ending at sample 283 scores really well, in practice you will later see that this would fire at too many points (i.e. multiple times per bit).\n",
    "> \n",
    "> Good results were obtained with a 96-sample reference starting at sample 149.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def calc_sads(ref_trace, traces, start, length=96):\n",
    "    stop = start+length\n",
    "    sads = np.zeros(len(traces))\n",
    "    a = ref_trace[start:stop]\n",
    "    for j,t in enumerate(traces):\n",
    "        b = t.wave[start:stop]\n",
    "        sad = 0\n",
    "        for i in range(len(a)):\n",
    "            asamp = int(a[i])\n",
    "            bsamp = b[i]\n",
    "            sad += abs(asamp-bsamp)\n",
    "        sads[j] = sad\n",
    "    return sads\n",
    "\n",
    "bestmax = [2**16, 0]\n",
    "bestavg = [2**16, 0]\n",
    "bestvar = [2**16, 0]\n",
    "maxmax = 0\n",
    "\n",
    "for start in range(0, 151-96):\n",
    "    sads = calc_sads(avg_trace, rtraces, start)\n",
    "    maxsad = np.max(sads)\n",
    "    avgsad = np.average(sads)\n",
    "    varsad = np.var(sads)\n",
    "    if maxsad < bestmax[0]:\n",
    "        bestmax = [maxsad, start]\n",
    "    if avgsad < bestavg[0]:\n",
    "        bestavg = [avgsad, start]\n",
    "    if varsad < bestvar[0]:\n",
    "        bestvar = [varsad, start]\n",
    "    if maxsad > maxmax:\n",
    "        maxmax = maxsad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Starting SAD point with the lowest maximum SAD score (%d): %d' % (bestmax[0], bestmax[1]))\n",
    "print('Starting SAD point with the lowest SAD variance (%d): %d' % (bestvar[0], bestvar[1]))\n",
    "print('Starting SAD point with the lowest average SAD score (%d): %d' % (bestavg[0], bestavg[1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The \"best starting SAD points\" can vary from run to run; this attack was developed using 35 as a starting point.\n",
    "\n",
    "Even if you obtained different \"best points\" above, you should find the metrics for 35 in line with the results above. (You can also choose to go ahead with a different starting point but you'll have to modify some things later in this notebook accordingly.)\n",
    "\n",
    "> **⚠️ SAM4S notes**: don't forget to adjust `starting_sample` (we suggest 149)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "starting_sample = 35\n",
    "sads = calc_sads(avg_trace, rtraces, starting_sample)\n",
    "print('Max: %d (best max: %d)' % (np.max(sads), bestmax[0]))\n",
    "print('Avg: %d (best avg: %d)' % (np.average(sads), bestavg[0]))\n",
    "print('Var: %d (best var: %d)' % (np.var(sads), bestvar[0]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# avg_trace is floats; change it to a np.ndarray of uint8's for setting scope.SAD.reference\n",
    "ref_trace = np.zeros(scope.SAD.sad_reference_length, dtype=np.uint8)\n",
    "for i in range(scope.SAD.sad_reference_length):\n",
    "    if i < 96:\n",
    "        ref_trace[i] = np.uint8(avg_trace[i+starting_sample])\n",
    "    else:\n",
    "        ref_trace[i] = np.uint8(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Configure the SAD module:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.SAD.trigger_sample = 96\n",
    "scope.SAD.reference = np.asarray(ref_trace, dtype=np.uint8)\n",
    "scope.SAD.multiple_triggers = True\n",
    "scope.SAD.always_armed = True\n",
    "scope.trigger.module = 'SAD'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we set the `scope.adc` parameters to grab the POI samples that we need for our attack, keeping in mind the SAD trigger module's latency.\n",
    "\n",
    "A capture triggered by SAD starts `scope.SAD.trigger_sample + scope.SAD.latency` after the first SAD sample, which is sample number `starting_sample` (35).\n",
    "\n",
    "This means that the capture starts on sample `starting_sample + scope.SAD.trigger_sample + scope.SAD.latency`.\n",
    "\n",
    "We'll use `scope.adc.presamples` to effectively \"go back in time\" and capture the samples that we want to see."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "presamples = starting_sample + scope.SAD.sad_reference_length + scope.SAD.latency - int(abs(poi[0])) - START\n",
    "samples = presamples + (3 - (presamples%3)) # samples must be a multiple of 3\n",
    "assert (samples > (abs(poi[-1]) - abs(poi[0]))), \"Increase samples so that all POI can be captured\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.adc.samples = samples\n",
    "scope.adc.presamples = presamples\n",
    "scope.adc.offset = 0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The last SAD settings we need to tune are `scope.SAD.threshold` and `scope.SAD.interval_threshold`.\n",
    "\n",
    "We could use the ChipWhisperer SAD model, `cw.SADModelWrapper()`, but since the full target operation is around 5M samples long, this would be **very** slow.\n",
    "\n",
    "Instead let's use `SADExplorer`. These threshold settings should be a good starting point; `SADExplorer` will help us confirm this visually:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.SAD.threshold = 5\n",
    "scope.SAD.interval_threshold = 10"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#explorer = cw.SADExplorer(scope, target, np.asarray(ref_trace, dtype=np.uint8), 0, max_segments=255, capture_function=lambda: capture_ecc_trace(k))\n",
    "explorer = cw.SADExplorer(scope, target, ref_trace, 0, max_segments=255, capture_function=lambda: capture_ecc_trace(k))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that if the thresholds are increased, some of the captured segments start diverging in the first few samples, and the trigger times suggest that triggers are not always occuring where they are meant to.\n",
    "\n",
    "When you get a succesful SAD capture, it's important to check that the SAD module triggered 255 or 256 times(*)- no more, no less.\n",
    "\n",
    "(\\*) why 255 or 256? If you refer back to uECC.c, recall that we are triggering in the `for` loop, near the call to `XCcZ_addC()`; this loop iterates 255 times, but `XCcZ_addC()` is also called one last time after the loop, and that may (or may not) trigger a SAD match\n",
    "\n",
    "To do this, check the value of `scope.SAD.num_triggers_seen` after a capture. It's important that `scope.SAD.always_armed` be set, otherwise `scope.SAD.num_triggers` stops counting after the capture is done (i.e. after `scope.adc.segments` have been captured)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert scope.SAD.always_armed and (scope.SAD.num_triggers_seen in [255, 256]), 'got %d triggers!' % scope.SAD.num_triggers_seen"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's try another capture-- this should work reliably:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED': # no point in doing this step with pre-recorded traces\n",
    "    Px, Py = new_point()\n",
    "    strace = capture_ecc_trace(k, Px, Py)\n",
    "    if scope.SAD.always_armed and scope.SAD.num_triggers_seen in [255,256]:\n",
    "        print('Looks good! Got %d triggers. ✅' % scope.SAD.num_triggers_seen)\n",
    "    else:\n",
    "        print('❌ Got %d triggers; try again.' % scope.SAD.num_triggers_seen)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once you get the right number of triggers, it's worth checking that there are no outliers in their timestamps:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **⚠️ SAM4S notes**: the expected range for `ttimes` is 26000 to 28000."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED': # no point in doing this step with pre-recorded traces\n",
    "    ttimes = scope.trigger.get_trigger_times()\n",
    "    assert 20000 < min(ttimes) < 23000\n",
    "    assert 20000 < max(ttimes) < 23000"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED': # no point in doing this step with pre-recorded traces\n",
    "    print(min(ttimes), max(ttimes))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also check visually check whether the captured segments are aligning as they should:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED': # no point in doing this step with pre-recorded traces\n",
    "    samples = scope.adc.samples\n",
    "    B = figure(width=1800)\n",
    "    colors = itertools.cycle(inferno(255))\n",
    "    for i in range(255):\n",
    "        B.line(list(range(samples)), strace.wave[i*samples:(i+1)*samples], color=next(colors))\n",
    "    show(B)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, if you have a logic analyzer, you can drive the SAD's trigger onto Husky's trigger/glitch output MCX port (or USERIO), and confirm visually whether the SAD is triggering everytime that the target FW issues a trigger on GPIO4.\n",
    "\n",
    "If all is good, let's proceed to capture lots of traces for an attack.\n",
    "\n",
    "With a good SAD threshold, most captures should be \"good\", but there may be some outliers which we'll simply discard (these will be identified based on the number of triggers and trigger times).\n",
    "\n",
    "Let's also use a random $k$, to set ourselves up for an attack with these traces.\n",
    "\n",
    "Finally we need to adjust our `scope.adc` settings so that our POI are contained in each captured segment.\n",
    "\n",
    "Recall that we used `starting_sample = 35` for our SAD reference; the SAD trigger then occurs `scope.SAD.trigger_sample + scope.SAD.latency = 106` samples later.\n",
    "\n",
    "We want trace collection for each segment to begin on the first POI sample, which was sample 82 (adjust if you used a different set of POI). We do this by setting `scope.adc.presamples` to 35 + 106 - 82. (To understand how we get this, it helps to draw these points on a line.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "presamples = starting_sample + scope.SAD.trigger_sample + scope.SAD.latency - 82 # our initial POI sample was 82 (prior to setting the offset to 80 earlier in the notebook!); adjust accordingly if you used something else\n",
    "samples = presamples + (6 - (presamples%3))\n",
    "\n",
    "scope.adc.samples = samples\n",
    "scope.adc.presamples = presamples\n",
    "scope.adc.offset = 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "k = random_k()\n",
    "kr = regularized_k(k)\n",
    "hex(k), hex(kr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We turn on the `check_sad_triggers` and `check_ttimes` options, to discard traces that don't look good.\n",
    "\n",
    "Note the `trim=13` argument is used to save the minimum number of samples per segment; if you've diverged from the standard `poi` values, make sure that `trim` is large enough to keep all of the POI samples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "straces = capture_ecc_traces(k, N=60, step='6', check_sad_triggers=True, check_ttimes=True, trim=13)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES == 'SIMULATED':\n",
    "    k = straces[0].textin['k']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A few discarded traces is ok, but if you get a lot, go back to `SADExplorer` and adjust the thresholds to obtain more reliable captures, or increase `N` to compensate."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We also need to shift the POI array back to start from 0, since the POI samples are now located starting at sample 0 in our captures:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "shift = abs(poi[0])\n",
    "for i in range(len(poi)):\n",
    "    if poi[i] >= 0:\n",
    "        poi[i] -= shift\n",
    "    else:\n",
    "        poi[i] += shift"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we're ready to try the attack; it should work exactly as before."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sd = calc_sumdata(poi, straces, trim=13)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = figure(width=2000)\n",
    "\n",
    "xrange = list(range(len(sd)))\n",
    "s.line(xrange, sd, line_color=\"red\", line_width=2)\n",
    "\n",
    "show(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "attack(poi, straces, trim=13)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sadtrigger_wrong_bits = []\n",
    "for attack_traces in range(1, len(straces)+1):\n",
    "    print('Attacking with %d traces... ' % attack_traces,  end='')\n",
    "    wrong_bits = attack(poi, straces[:attack_traces], 13, False)\n",
    "    if wrong_bits:\n",
    "        print('failed, %d wrong bits' % len(wrong_bits))\n",
    "    else:\n",
    "        print('passed ✅')\n",
    "    sadtrigger_wrong_bits.append(len(wrong_bits))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should find that the attack works just as well as before:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = figure(width=2000, x_axis_label='Number of traces', y_axis_label='Number of wrong bits')\n",
    "s.line(list(range(len(fwtrigger_wrong_bits))), fwtrigger_wrong_bits, color='blue')\n",
    "s.line(list(range(len(fwtrigger_wrong_bits))), sadtrigger_wrong_bits, color='red')\n",
    "show(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Removing the firmware trigger\n",
    "\n",
    "Although we're triggering our captures using only SAD and carried out a successful attack with these SAD-captured traces, the firmware is still pulsing GPIO4 255 times in its PMUL loop.\n",
    "\n",
    "Can the attack succeed without any modifications if we remove the GPIO4 trigger? The answer is not necessarily yes. Turning on a GPIO draws a fair amount of current and can certainly influence power traces (see for example [this note](https://github.com/newaetech/tracewhisperer/blob/master/trace_noise.md) on the effect of trace activity on power traces). Could this effect be contributing to the success of SAD triggering?\n",
    "\n",
    "So let's see: we'll switch to a different firmware, compiled from the same source but with the `trigger_high()` and `trigger_low()` calls ifdef'd out."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fw_path = '../../../firmware/mcu/simpleserial-ecc-notrace/simpleserial-ecc-nofwtrigger-{}.hex'.format(PLATFORM)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED':\n",
    "    cw.program_target(scope, prog, fw_path)\n",
    "\n",
    "    target.simpleserial_write('i', b'')\n",
    "    time.sleep(0.1)\n",
    "    print(target.read())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's see what happens if we try to capture using our existing SAD reference..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED':\n",
    "    ptrace = capture_ecc_trace(k, Px, Py)\n",
    "    print('SAD triggered %d times' % scope.SAD.num_triggers_seen)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED':\n",
    "    scope.errors.clear()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The results can vary significantly from run to run, but usually SAD will fail to consistently trigger 255 times (although from time to time it *can* work). Whether it's the absence of the GPIO4 pulse, or that the recompilation of the ECC code has affected the power trace; we're not done yet.\n",
    "\n",
    "When this notebook was first developed, the Husky SAD implementation was \"true SAD\" (there was no `scope.SAD.interval_threshold` parameter), and SAD captures with the trigger-less FW would fail much more dramatically; this section was developed to show how to tweak the SAD reference and get back to successful captures.\n",
    "\n",
    "Now that Husky uses interval-based SAD, there is an easier solution: if you increase `scope.SAD.interval_threshold` a bit, you should be able to get consistently good captures, and a successful attack. You can try repeating the previous section (using `SADEXplorer` to tweak the SAD parameters) to do this.\n",
    "\n",
    "However we've retained this section to teach how the SAD reference can be tweaked, or \"ported\" from one firmware to another.\n",
    "\n",
    "Choose your own adventure!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## OPTION 1:\n",
    "\n",
    "**This is the recommended option!**\n",
    "\n",
    "Here we keep the current SAD reference and adjust `scope.SAD.interval_threshold` to get captures working successfully again.\n",
    "\n",
    "We need to reset these `scope.adc` parameters; then go back to the `SADExplorer` instantiation above and tweak `scope.SAD.interval_threshold` and `scope.SAD.threshold` until you get consistently good captures, and the attack succeeds again."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "original_samples = scope.adc.samples\n",
    "original_presamples = scope.adc.presamples\n",
    "\n",
    "presamples = starting_sample + scope.SAD.sad_reference_length + scope.SAD.latency - 2 - START # 2 is for the previous value of poi[0], which has since been shifted down to 0\n",
    "samples = presamples + (3 - (presamples%3)) # samples must be a multiple of 3\n",
    "assert (samples > (abs(poi[-1]) - abs(poi[0]))), \"Increase samples so that all POI can be captured\"\n",
    "\n",
    "scope.adc.samples = samples\n",
    "scope.adc.presamples = presamples\n",
    "scope.adc.offset = 0\n",
    "\n",
    "print('Now go back to the SADExplorer; do not proceed below.')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you inadvertently ran the cell above but wish to proceed with option 2 instead, make sure to reset `samples` and `presamples` to what they should be:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    if original_samples:\n",
    "        scope.adc.samples = original_samples\n",
    "        scope.adc.presamples = original_presamples\n",
    "except:\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## OPTION 2:\n",
    "\n",
    "Before proceeding with option 2, understand that option 1 is the preferred option: it is much more simple and it works very well.\n",
    "\n",
    "Option 2 is presented here to illustrate just how useful the interval threshold setting is; it's also presented to teach one way to handle situations where maybe ajusting the interval threshold isn't sufficient.\n",
    "\n",
    "If you did try option 1, the `SADExplorer` probably made you notice that the traces from the trigger-less firmware are simply shifted up a bit. We could try to heuristically adjust our reference. This is essentially what we'll do here, but in a more precise manner.\n",
    "\n",
    "We'll start by capturing the full PMUL trace again.\n",
    "\n",
    "But how do we trigger our capture? Again there are different approaches. ADC-level triggering is an option, depending on the target. On the STM32F3, the idle power isn't much lower than the \"active\" power, so that might not work great.\n",
    "\n",
    "Let's instead trigger on the UART messages that kick off the target operation.\n",
    "\n",
    "When `capture_ecc_trace()` is run, the last thing sent to the target is $k$, so let's trigger on that."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED':\n",
    "    scope.UARTTrigger.enabled = True\n",
    "    scope.UARTTrigger.baud = int(target.baud)\n",
    "    scope.UARTTrigger.set_pattern_match(0, 'k' + ('%064x' % k)[:7]) # match the 'k'... command that we send, which is what kicks off the PMUL operation:\n",
    "    scope.UARTTrigger.trigger_source = 0\n",
    "    scope.UARTTrigger.rules_enabled = [0]\n",
    "\n",
    "    scope.trigger.module = 'UART'\n",
    "    scope.trigger.triggers = 'tio2'\n",
    "\n",
    "    assert scope.trace.clock.swo_clock_locked"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.adc.samples = int(6e6)\n",
    "scope.adc.presamples = 0\n",
    "scope.adc.offset = 0\n",
    "scope.adc.stream_mode = True\n",
    "scope.adc.segments = 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fulltrace = capture_ecc_traces(k, N=1, step='7')[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import holoviews as hv\n",
    "from holoviews.operation import decimate\n",
    "from holoviews.operation.datashader import datashade\n",
    "hv.extension('bokeh')\n",
    "datashade(hv.Curve(fulltrace.wave)).opts(width=2000, height=900)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At this point it might be easier to understand why SAD matching is not working by computing SAD scores in software.\n",
    "\n",
    "Let's study a slice of the full capture. We know that processing one bit of $k$ takes around 21K cycles.\n",
    "\n",
    "It then stands that any slice of 100K samples should contain 4 or 5 full bits.\n",
    "\n",
    "We'll pick some point in the middle of the target operation and look for our SAD pattern."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def calc_sad(ref_trace, wave):\n",
    "    sad = 0\n",
    "    for i in range(len(ref_trace)):\n",
    "        #asamp = int(256*(ref_trace[i]+0.5))\n",
    "        #bsamp = int(256*(wave[i]+0.5))\n",
    "        asamp = int(ref_trace[i])\n",
    "        bsamp = wave[i]\n",
    "        sad += abs(asamp-bsamp)\n",
    "    return sad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Do SW SAD over a small range, where we can more easily spot a smaller number of SAD matches:\n",
    "start = int(3e6)\n",
    "stop = int(start + 100e3)\n",
    "\n",
    "sads = []\n",
    "for i in tnrange(start, stop):\n",
    "    #sads.append(calc_sad(avg_trace[54:54+96], fulltrace.wave[i:i+96]))\n",
    "    sads.append(calc_sad(ref_trace[:96], fulltrace.wave[i:i+96]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "B = figure(width=1800)\n",
    "B.line(list(range(len(sads))), sads)\n",
    "show(B)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The SAD score has some dips but they are not very distinct.\n",
    "\n",
    "This is a pure SAD computation, not the interval-based SAD used by Husky, but for reference, the pure SAD score for the original Husky was around 200.\n",
    "\n",
    "Here the low peaks are much higher:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Minimum SW-calculated SAD for trigger-less FW: %d' % min(sads))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This SAD plot appears to have some periodicity, but not enough to confidently establish markers that distinguish our 4 $k$ bits.\n",
    "\n",
    "(If you're lucky you may see 4 distinct minimums, however this isn't always the case.)\n",
    "\n",
    "At this point it's useful to remember two very important points:\n",
    "1. ChipWhisperer does not measure the actual power consumption of the target. It measure the voltage drop across a shunt resistor, and there are all sorts of analog effects that you may have noticed before.\n",
    "2. The SAD computation done by Husky does not accomodate shifted or scaled power traces. If the SAD reference is actually contained in the new firmware's power trace but in a shifted, scaled, or stretched form, Husky's SAD trigger won't match it well.\n",
    "\n",
    "However by doing SAD matching in *software*, we can easily compensate for shifts and stretches.\n",
    "\n",
    "The idea is really simple: prior to every SAD computation, shift and scale the candidate trace so that it matches the *range* of the SAD reference. Thus, is a shifted/scaled version of the SAD reference exists in the power trace, we will find it.\n",
    "\n",
    "If this works, we can then define a new SAD reference, and successfully go back to SAD triggering on the ChipWhisperer capture hardware."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def calc_sad_scaled(ref_trace, wave):\n",
    "    refmin = min(ref_trace)\n",
    "    refmax = max(ref_trace)\n",
    "    refrange = refmax - refmin\n",
    "\n",
    "    rawmin = min(wave)\n",
    "    rawmax = max(wave)\n",
    "    rawrange = rawmax - rawmin\n",
    "\n",
    "    scaled = np.asarray(wave, dtype=np.float64) - rawmin\n",
    "    scaled *= refrange/rawrange\n",
    "    scaled += refmin\n",
    "\n",
    "    sad = 0\n",
    "    for i in range(len(ref_trace)):\n",
    "        #asamp = int(256*(ref_trace[i]+0.5))\n",
    "        #bsamp = int(256*(scaled[i]+0.5))\n",
    "        asamp = int(ref_trace[i])\n",
    "        bsamp = int(scaled[i])\n",
    "        sad += abs(asamp-bsamp)\n",
    "    return sad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scaled_sads = []\n",
    "for i in tnrange(start, stop):\n",
    "    scaled_sads.append(calc_sad_scaled(ref_trace[:96], fulltrace.wave[i:i+96]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "B = figure(width=1800)\n",
    "B.line(list(range(len(scaled_sads))), scaled_sads)\n",
    "show(B)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is an improvement! The low peaks should now be both lower and more distinct from other peaks. With an appropriate `THRESHOLD` value we may be able to find appropriately-distanced match points:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "THRESHOLD = 210\n",
    "matches = np.where(np.asarray(scaled_sads)  < THRESHOLD)[0]\n",
    "deltas = []\n",
    "for m in range(1, len(matches)):\n",
    "    deltas.append(matches[m] - matches[m-1])\n",
    "print(deltas)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ideally we'd like to find three deltas, each of around 21K, but don't stress too much if you can't get three nice deltas; it turns out we don't need the \"ideal\" scenario to improve our SAD reference.\n",
    "\n",
    "Let's run the scaled SAD match on the full waveform:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scaled_sads = []\n",
    "for i in tnrange(len(fulltrace.wave)-96):\n",
    "    scaled_sads.append(calc_sad_scaled(ref_trace[:96], fulltrace.wave[i:i+96]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*If you have a slow computer, you can shorten this by doing the calculation over a fraction of the waveform, instead of the full trace; for example you can change the `tnrange()` argument to `len(fulltrace.wave)//4-96`, in which case you would expect to get a quarter of the matches.*\n",
    "\n",
    "*However it's best to run on the full trace: this is slow but one-time computation, and using the full trace will result in a  better reference.*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We want to pick a threshold that results in approximately 255 matches. However it doesn't need to be exactly 255, so don't try too hard:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "THRESHOLD = 250\n",
    "matches = len(np.where(np.asarray(scaled_sads) < THRESHOLD)[0])\n",
    "assert 200 < matches <= 256, 'Not a good number of matches (%d), adjust THRESHOLD' % matches"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we look at the time delta between successive matches:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "found = np.where(np.asarray(scaled_sads) < THRESHOLD)[0]\n",
    "deltas = []\n",
    "for i in range(1, len(found)):\n",
    "    deltas.append(found[i] - found[i-1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "B = figure(width=1800)\n",
    "B.line(list(range(len(deltas))), deltas)\n",
    "show(B)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We're going to assume that deltas of ~22000 cycles are the \"correct\" matches, and that others are \"bad\" matches that are due to untuned threshold and/or SAD reference.\n",
    "\n",
    "The idea then is to take all \"correct\" matches and average them to synthesize a better SAD reference.\n",
    "\n",
    "> **⚠️ SAM4S notes**: recall that the expected range for successive triggers shifts to between 26000 and 28000."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_ref = np.zeros(scope.SAD.sad_reference_length)\n",
    "used_segments = 0\n",
    "for i in range(1, len(found)):\n",
    "    if 20000 < found[i] - found[i-1] < 25000:\n",
    "        used_segments += 1\n",
    "        new_ref += fulltrace.wave[found[i]:found[i]+scope.SAD.sad_reference_length]\n",
    "new_ref //= used_segments\n",
    "print('Used %d SW-matched references to build the new SAD reference.' % used_segments)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "How does the new reference compare to the old one? Let's compute the SAD between our original reference and our new synthesized (and hopefully improved) reference:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "calc_sad(ref_trace[:96], new_ref[:96])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A large SAD value explains why our initial attempt at capturing with the FW-trigger-derived reference was totally unsuccessful.\n",
    "\n",
    "Let's visually compare the original and improved references:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "B = figure(width=1800)\n",
    "B.line(list(range(96)), ref_trace[:96], line_color='black')\n",
    "B.line(list(range(96)), new_ref[:96], line_color='red')\n",
    "show(B)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should find that the new synthesized reference is essentially a shifted-down version of the original reference.\n",
    "\n",
    "This also explains why **option 1** is viable: increasing `scope.SAD.interval_threshold` should reliably admit the power traces segments of the new firmware. Of course, using the improved reference will be more reliable.\n",
    "\n",
    "Also: using option 1, we could have simply eyeballed the traces and noticed that a simple shift to the reference is all that was needed...\n",
    "\n",
    "We are now finally(!) ready to move on to a Husky SAD-triggered capture."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.SAD.reference = new_ref.astype(np.uint8)\n",
    "scope.trigger.module = 'SAD'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.adc.stream_mode = False\n",
    "scope.adc.segments = 255\n",
    "\n",
    "# see previous section for how these values were obtained:\n",
    "scope.adc.samples = samples\n",
    "scope.adc.presamples = presamples\n",
    "scope.adc.offset = 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED':\n",
    "    strace = capture_ecc_trace(k, Px, Py)\n",
    "    if scope.SAD.num_triggers_seen in [255,256]:\n",
    "        print('Looks good! Got %d triggers. ✅' % scope.SAD.num_triggers_seen)\n",
    "    else:\n",
    "        print('❌ Got %d triggers; try again.' % scope.SAD.num_triggers_seen)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Adjust the threshold until you reliably get the right number of triggers. \n",
    "\n",
    "Then we check that there are no outliers in their timestamps:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED':\n",
    "    ttimes = scope.trigger.get_trigger_times()\n",
    "    assert 20000 < min(ttimes) < 23000\n",
    "    assert 20000 < max(ttimes) < 23000"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if TRACES != 'SIMULATED':\n",
    "    print(min(ttimes), max(ttimes))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We should now be able to reliably capture traces for the attack. Some discarded traces is to be expected, but the majority of captures should succeed.\n",
    "\n",
    "(If too many traces are getting discarded, return to the `SADExplorer` to tune the thresholds.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "k = random_k()\n",
    "kr = regularized_k(k)\n",
    "hex(k), hex(kr)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **⚠️ SAM4S notes**: around 150 good traces are required.\n",
    "> With the STM32 target, it's usually possible to tune the SAD reference and threshold so that virtually every trace capture is a good one.\n",
    "> \n",
    "> With the SAM4S, this seems more difficult; expect to lose a few traces, due to both not enough and too many triggers. You can simply compensate by increasing N."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "straces = capture_ecc_traces(k, N=60, step='8', check_sad_triggers=True, check_ttimes=True, trim=13)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# In the case of pre-recorded traces, set k to what was used for those traces:\n",
    "if TRACES == 'SIMULATED':\n",
    "    k = straces[0].textin['k']\n",
    "    kr = regularized_k(k)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sd = calc_sumdata(poi, straces, trim=13)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = figure(width=2000)\n",
    "\n",
    "xrange = list(range(len(sd)))\n",
    "s.line(xrange, sd, line_color=\"red\", line_width=2)\n",
    "\n",
    "show(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If your plot looks good, proceed to guessing all the bits.\n",
    "\n",
    "Otherwise, you can try a tighter threshold, or simply try a fresh trace acquisition."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "attack(poi, straces, 13)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "finalattack_wrong_bits = []\n",
    "for attack_traces in range(1, len(straces)+1):\n",
    "    print('Attacking with %d traces... ' % attack_traces,  end='')\n",
    "    wrong_bits = attack(poi, straces[:attack_traces], 13, False)\n",
    "    if wrong_bits:\n",
    "        print('failed, %d wrong bits' % len(wrong_bits))\n",
    "    else:\n",
    "        print('passed ✅')\n",
    "    finalattack_wrong_bits.append(len(wrong_bits))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's compare the attack success rates we've obtained for:\n",
    "1. the original trigger-based capture (blue)\n",
    "2. the SAD-based capture with the trigger still active (red)\n",
    "3. the SAD-based capture with no trigger (green)\n",
    "\n",
    "YMMV but all three attacks should have a very similar success rate."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = figure(width=2000, x_axis_label='Number of traces', y_axis_label='Number of wrong bits')\n",
    "s.line(list(range(len(fwtrigger_wrong_bits))), fwtrigger_wrong_bits, color='blue')\n",
    "s.line(list(range(len(fwtrigger_wrong_bits))), sadtrigger_wrong_bits, color='red')\n",
    "s.line(list(range(len(finalattack_wrong_bits))), finalattack_wrong_bits, color='green', line_width=2)\n",
    "show(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Conclusion"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you're so inclined, you can take this attack further:\n",
    "- it's likely possible to improve these results (better POI?)\n",
    "- apply the Hidden Number Problem to transform this into a more realistic attack (see the [uecc_part1_trace.ipynb](uecc_part1_trace.ipynb) notebook for a discussion of HNP)\n",
    "\n",
    "If you completed this notebook and part 1, it's highly recommended to move on to [part 3](uecc_part3_trace_sad.ipynb) to learn about the advantages of *combining* trace and SAD triggering."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python (venv39)",
   "language": "python",
   "name": "venv39"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
